#include "numrule.h"
#include <string>
#include <stdlib.h>
#include <iostream>
#include "calcexception.h"

using namespace std;

/** Static member fields. 
 * well, not member fields, but they are unused elsewhere
 */
static const string NUMRULE_FLAG_FIRST    = "must_be_first";
static const string NUMRULE_FLAG_SUBTOTAL = "subtotal";

/** Construct rule from key an value.
 * Both the key and the value are sliced up and stored.
 * Interpretation of semantics or anything else is NOT done here, but exception
 * is thrown if something critical is missing.
 */
NumRule::NumRule(string key, string value) {
  // Dissect key: both STATE and literal must exist.
  int pos = key.find(",");
  if (pos<=0) {
    string message = string("NumRule: Key <") + key + "> must be in form STATE,literal";
    throw CalcException(message);
  }
  state = key.substr(0,pos);
  literal = key.substr(pos+1);

  // Dissect value: semantics must exist, but the rest is optional.
  flags.clear();
  pos = value.find(":");
  if (pos<0) { // no new_state, no flags
    semantics = value;
    new_state = "";
  } else {     // have new_state at least
    semantics = value.substr(0,pos);
    string val2 = value.substr(pos+1);
    pos = val2.find(":");
    if (pos < 0) { // no flags
      new_state = val2;
    } else {       // have flags
      new_state = val2.substr(0,pos);
      val2 = val2.substr(pos+1);
      // dissect flags
      while ((pos = val2.find(","))>0) {
	string flag = val2.substr(0,pos);
	flags.insert(flag);
	val2 = val2.substr(pos+1);
      }
      // insert last flag
      flags.insert(val2);
    }
  }
}

/** Apply rule to number.
 * Semantics are applied. 
 * Flags are checked/added/removed.
 * New state is returned.
 */
string NumRule::apply (Number &n) const {
  // TODO check state... here or outside?
  
  // apply semantics. As opposed to native parser, here we are lazily ignoring
  // any possible errors with parsing. Normally, it's not user input in the 
  // first place.
  long semval = atol(semantics.substr(1).c_str());
  switch (semantics[0]) {
    case '+':
      n.value += semval;
      break;
    case '*':
      if (!n.value)
	n.value = semval;
      else
	n.value *= semval;
      break;

    default:
      string message = string("NumRule::apply: Semantics for <") + semantics + "> undefined";
      throw CalcException(message);
  }

  // apply flags
  for (set<string>::iterator it = flags.begin(); it!=flags.end(); it++) {
    if ((*it)[0]=='!') {
      // Flags preceded by ! are checked against.
      string myflag = (*it).substr(1);
      // the flag must NOT be set
      if (n.flags.find(myflag)!=n.flags.end()) {
	string message = string("NumRule::apply: flag <") + myflag + "> must not be set at this point";
	throw CalcException(message);
      }
    } else if ((*it)[0]=='-') {
      // Flags preceded by - are removed from a number's flags.
      string myflag = (*it).substr(1);
      // the flag must be removed
      n.flags.erase(myflag);
    } else if ((*it) == NUMRULE_FLAG_FIRST) {
      // the FIRST flag is special.
      if (!n.first) {
	string message = string("NumRule::apply: literal <") + literal + "> must be first literal";
	throw CalcException(message);
      }
    } else if ((*it) == NUMRULE_FLAG_SUBTOTAL) {
      // The subtotal flag is special as well.
      n.subtotal += n.value;
      n.value = 0;
    } else {
      // All other flags are checked and then added.
      if (n.flags.find(*it)!=n.flags.end()) {
	cout << "Flag " << (*it) << " already set" << endl;
	string message = string("NumRule::apply: flag <") + (*it) + "> already set at this point";
	throw CalcException(message);
      } else {
	n.flags.insert(*it);
      }
    }
  }

  // no matter what, a literal has been processed.
  // Actually, this should not be the case if only a sign was processed... 
  // here comes another improvement opportunity (handle have_minus here as well)
  n.first = false;

  // set new state
  if (new_state=="") {
    // this is a bit funny, because in global rules, "*" is returned.
    return state;
  } else {
    return new_state;
  }
}
